Danfo.js – PandasライクなJavaScriptのDataFrameライブラリ-
Introduction
例えば機械学習のトレーニングデータを用意するとき、
csvからデータを読み込んだり列や行を追加・削除・変更したりグルーピングしたりなどなど、
データの加工がほぼ必須です。
そういったときにデータ解析用ライブラリとしてPythonのPandasなどがよく使われます。
とても使いやすいライブラリなのですが、
Pythonに不慣れな私としてはJavascriptでなんとかしたいなと思っていました。
そこで探してみたところ、みつけたのがDanfo.jsです。
Environment
- OS : MacOS 10.15.7
- Node : v14.16.1
Danfo.js?
Danfo.jsとは、PythonのデータフレームライブラリであるPandasにインスパイアされた、
構造化データの操作や処理を実施するためのJavascriptライブラリです。
(おそらく)Pandasを使える人ならすぐに馴染めると思われます。
csvやjsonファイル、TensorflowのオブジェクトからDataFrameを構築したり、
それらのDataFrameに対して変更や結合、データのグルーピングなどいろいろな操作も提供しています。
Setup
npmでインストールします。
% npm install danfojs-node
(2021/8月時点)
danfojsはtensorflow.jsに依存しているようで、M1 Macでは動きませんでした。
また、tensor 2dをdanfojsにそのまま渡すことができるみたいでしたが、
自分の環境ではエラーだった(多分バージョンとかそのへんの問題)のでそこは未確認です。
Use Danfojs
ではつかってみましょう。
まずは配列データからDataFrameを作成して表示してみます。
(printでコンソール表示)
//danfojs-main.js const dfd = require("danfojs-node"); const arr_data = [[1, 'hello', 3.2], [5, 'danfojs',7.5],[3, 'javascipt',10.0]]; df = new dfd.DataFrame(arr_data) df.print()
% node danfojs-main.js ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ 0 │ 1 │ 2 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ 1 │ hello │ 3.2 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 5 │ danfojs │ 7.5 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ 3 │ javascipt │ 10 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
ctypesで型情報がわかる。
df.ctypes.print()
╔═══╤══════════════════════╗ ║ │ 0 ║ ╟───┼──────────────────────╢ ║ 0 │ int32 ║ ╟───┼──────────────────────╢ ║ 1 │ string ║ ╟───┼──────────────────────╢ ║ 2 │ float32 ║ ╚═══╧══════════════════════╝
headで頭から、tailで後ろから任意の行数を取得。
df.head(1).print() df.tail(1).print()
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ 0 │ 1 │ 2 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ 1 │ hello │ 3.2 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝ ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ 0 │ 1 │ 2 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ 3 │ javascipt │ 10 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
column指定も可能。
describeでサマリ情報取得。
const arr_data = [[1, 'hello', 3.2], [5, 'danfojs',7.5],[3, 'javascipt',10.0]]; df = new dfd.DataFrame(arr_data,{columns: ["id", "name", "number"]}); console.log(df.index); console.log(df.columns); df.describe().print()
[ 0, 1, 2 ] [ 'id', 'u_name', 'number' ] ╔══════════╤═══════════════════╤═══════════════════╗ ║ │ id │ number ║ ╟──────────┼───────────────────┼───────────────────╢ ║ count │ 3 │ 3 ║ ╟──────────┼───────────────────┼───────────────────╢ ║ mean │ 3 │ 6.9 ║ ╟──────────┼───────────────────┼───────────────────╢ ║ std │ 2 │ 3.439477 ║ ╟──────────┼───────────────────┼───────────────────╢ ║ min │ 1 │ 3.2 ║ ╟──────────┼───────────────────┼───────────────────╢ ║ median │ 3 │ 7.5 ║ ╟──────────┼───────────────────┼───────────────────╢ ║ max │ 5 │ 10 ║ ╟──────────┼───────────────────┼───────────────────╢ ║ variance │ 4 │ 11.83 ║ ╚══════════╧═══════════════════╧═══════════════════╝
sort可能。
df.sort_values({by: "number", ascending:false,inplace: true}); df.print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ id │ u_name │ number ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ 3 │ javascipt │ 10 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 5 │ danfojs │ 7.5 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ 1 │ hello │ 3.2 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
pandasでも使えるlocとかilocとか。
slice指定もできます。
console.log("loc") df.loc({rows: [0,1]}).print() console.log("iloc") df.iloc({rows: [1,2]}).print() console.log("slice") df.iloc({rows: ["0:3"]}).print()
loc ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ id │ u_name │ number ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ 1 │ hello │ 3.2 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 5 │ danfojs │ 7.5 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝ iloc ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ id │ u_name │ number ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 5 │ danfojs │ 7.5 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ 3 │ javascipt │ 10 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝ slice ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ id │ u_name │ number ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ 1 │ hello │ 3.2 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 5 │ danfojs │ 7.5 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ 3 │ javascipt │ 10 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
queryを使うとSQLのwhereみたいなかんじで行を抽出できます。
console.log("query-1") df.query({ "column": "number", "is": ">", "to": 5 }).print(); console.log("query-2") query_df = df.query({ column: "u_name", is: "==", to: "danfojs"}).print();
query-1 ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ id │ u_name │ number ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 5 │ danfojs │ 7.5 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ 3 │ javascipt │ 10 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝ query-2 ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ id │ u_name │ number ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 5 │ danfojs │ 7.5 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
dropnaを使えば不正データを除去できる。
const json_data = [ { A:1,B:'hello',C:NaN}, { A:5, B:'danfojs',C:7.5 }, { A:3, B:'javascipt',C:10.0 }, { A:NaN,B:null,C:3.3}]; df = new dfd.DataFrame(json_data); df.isna().print(); df.dropna({axis: 0}).print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ A │ B │ C ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ false │ false │ true ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ false │ false │ false ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ false │ false │ false ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 3 │ true │ true │ false ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝ ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ A │ B │ C ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ 5 │ danfojs │ 7.5 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 3 │ javascipt │ 10 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
applyをつかって各値に関数を適用できます。
const data = [[1, 2, 3], [4, 5, 6], [20, 30, 40], [39, 89, 78]] const cols = ["A", "B", "C"] const df = new dfd.DataFrame(data, { columns: cols }) function square(x) { return x ** 2 } df.apply({callable: square }).print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ A │ B │ C ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ 1 │ 4 │ 9 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ 16 │ 25 │ 36 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ 400 │ 900 │ 1600 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 3 │ 1521 │ 7921 │ 6084 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
concatでDataFrame同士を結合できます。
let data_1 = [['U1', 'taro'], ['U2', 'hanako'],['U3','takeshi']]; let data_2 = [['tokyo', 20],['saitama',30], ['okinawa',40]]; let colum1 = ['id', 'name']; let colum2 = ['address', 'age']; let df_1 = new dfd.DataFrame(data_1, { columns: colum1 }); let df_2 = new dfd.DataFrame(data_2, { columns: colum2 }); dfd.concat({ df_list: [df_1, df_2], axis: 1 }).print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ id │ name │ address │ age ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ U1 │ taro │ tokyo │ 20 ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ U2 │ hanako │ saitama │ 30 ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ U3 │ takeshi │ okinawa │ 40 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
Groupingもいろいろとできます。
let data_g ={'A': ['foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'foo'], 'B': ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'], 'C': [1,3,2,4,5,2,6,7], 'D': [3,2,4,1,5,6,7,8] } let df_g = new dfd.DataFrame(data_g); let grp = df_g.groupby(["A"]); grp.get_groups(["foo"]).print(); grp.col(["C"]).sum().print(); let grp_2 = df_g.groupby(["A","B"]) grp_2.col(["C"]).sum().print()
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ A │ B │ C │ D ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ foo │ one │ 1 │ 3 ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ foo │ two │ 2 │ 4 ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ foo │ two │ 5 │ 5 ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 3 │ foo │ one │ 6 │ 7 ║ ╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 4 │ foo │ three │ 7 │ 8 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╧═══════════════════╝ ╔═══╤═══════════════════╤═══════════════════╗ ║ │ A │ C_sum ║ ╟───┼───────────────────┼───────────────────╢ ║ 0 │ foo │ 21 ║ ╟───┼───────────────────┼───────────────────╢ ║ 1 │ bar │ 9 ║ ╚═══╧═══════════════════╧═══════════════════╝ ╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ A │ B │ C_sum ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ foo │ one │ 7 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ foo │ two │ 7 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 2 │ foo │ three │ 7 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 3 │ bar │ one │ 3 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 4 │ bar │ two │ 2 ║ ╟───┼───────────────────┼───────────────────┼───────────────────╢ ║ 5 │ bar │ three │ 4 ║ ╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
csvファイルを読み込むこともかんたんにできます。
ためしにこの記事でつかったペンギンデータ(penguins_size.csv)を
読んでみます。
const dfd = require("danfojs-node") async function exec() { let df = await dfd.read("path/your/penguins_size.csv"); console.log(df.shape); let df_schema = df .astype({column: "culmen_length_mm", dtype: "float32"}) .astype({column: "flipper_length_mm", dtype: "float32"}) .astype({column: "body_mass_g", dtype: "int32"}) .astype({column: "culmen_depth_mm", dtype: "float32"}); //replace NA to null let df_rep = df_schema.replace({ "replace": "NA", "with": null, "in": ["sex"] }); let df_new2 = df_rep.dropna({axis: 0}) df_new2.ctypes.print() df_new2.print(); } exec();
[ 344, 7 ] ╔═══════════════════╤══════════════════════╗ ║ │ 0 ║ ╟───────────────────┼──────────────────────╢ ║ species │ string ║ ╟───────────────────┼──────────────────────╢ ║ island │ string ║ ╟───────────────────┼──────────────────────╢ ║ culmen_length_mm │ float32 ║ ╟───────────────────┼──────────────────────╢ ║ culmen_depth_mm │ float32 ║ ╟───────────────────┼──────────────────────╢ ║ flipper_length_mm │ int32 ║ ╟───────────────────┼──────────────────────╢ ║ body_mass_g │ int32 ║ ╟───────────────────┼──────────────────────╢ ║ sex │ string ║ ╚═══════════════════╧══════════════════════╝ ╔════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╗ ║ │ species │ island │ culmen_length_mm │ culmen_depth_mm │ flipper_lengt... │ body_mass_g │ sex ║ ╟────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 0 │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ 3750 │ MALE ║ ╟────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ║ 1 │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ 3800 │ FEMALE ║ ╟────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢ ・・・・・・・・・
Summary
今回はJavascript用DataFrameライブラリ、Danfo.jsを使ってみました。
DataFrameのファイル出力(csvやjson)もかんたんにできますし、
データ整形が楽になりそうです。